package com.ccsoft.netty.heartbeat.client;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.ScheduledFuture;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author chanchaw
 * @create 2022-07-20 14:00
 */
public class ClientHandler extends ChannelInboundHandlerAdapter {
    private static final String HEART_BEAT_TIP = "heart beat!";// 发送给服务端心跳包中的内容 - 服务端会收到该字符串
    private static final String NICK_NAME = "新达纬编";// 硬编码客户端名称，上线后向服务端发送该ID

    private Random random = new Random();
    private int maxRandom = 20;
    private Channel channel;

    // 客户端连接到服务端后触发本事件
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        this.channel = ctx.channel();
        sendNickName(channel);//发送nickName
        ping(channel);
    }

    private void sendNickName(Channel channel){
        Map<String,String> data = new HashMap<>();
        data.put("nickName",NICK_NAME);
        if(channel.isActive()) channel.writeAndFlush(data.toString());
    }

    // 计算随机数，等待该时长后发送心跳包。当等待的时长超过服务端设定的出现读空闲的时长
    // 则服务端会出现超时没有收到数据 - 出现读空闲
    private void ping(Channel channel){
        int countDown = Math.max(1,random.nextInt(maxRandom));
        System.out.println("剩余" + countDown + "秒发送下一个心跳包...");
        // 指定定时任务，在 countDown 秒之后会执行内部 run 方法
        ScheduledFuture<?> future = channel.eventLoop().schedule(new Runnable() {
            // 如果通道关闭了还使用 writeAndFlush 写入数据则会报异常
            // 所以这里先判断是否可用
            @Override
            public void run() {
                if(channel.isActive()){
                    System.out.println("开始发送心跳包~");
                    channel.writeAndFlush(ClientHandler.HEART_BEAT_TIP);
                }else{
                    System.out.println("由于客户端连接断开，没有向其发送心跳包！");
                    channel.closeFuture();
                    throw new RuntimeException("由于客户端连接断开，没有向其发送心跳包！");
                }
            }
        },countDown, TimeUnit.SECONDS);

        // 上面的定时任务完成之后再次调用本方法 ping - 实现无限循环效果
        future.addListener(new GenericFutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                if(future.isSuccess()) ping(channel);
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
